package pro.ticktock.base.tree.impl;

import org.apache.commons.collections4.map.ListOrderedMap;
import pro.ticktock.base.tree.api.Entry;
import pro.ticktock.base.tree.api.HashEntryTreeNode;
import pro.ticktock.base.tree.api.KeyViolationException;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author lambert
 * @created 18-9-30
 */
public class HashEntryTreeNodeImpl<K, V>
        extends AbstractEntryTreeNode<HashEntryTreeNode<K, V>, K, V>
        implements HashEntryTreeNode<K, V> {
  ListOrderedMap<K, HashEntryTreeNode<K, V>> childMap;

  public HashEntryTreeNodeImpl() {
    this(null, null);
  }

  public HashEntryTreeNodeImpl(K key, V value) {
    childMap = new ListOrderedMap<>();
    setValue(makeEntry(key, value));
  }

  @Override
  public HashEntryTreeNode<K, V> getChildBy(K key) {
    return childMap.get(key);
  }

  @Override
  public Entry<K, V> getChildValueBy(K key) {
    HashEntryTreeNode<K, V> child = getChildBy(key);
    if (child == null) return null;
    else return child.getValue();
  }

  public V getChildEntryValueBy(K key) {
    Entry<K, V> node = getChildValueBy(key);
    if (node == null) return null;
    else return node.getValue();
  }

  @Override
  public Collection<HashEntryTreeNode<K, V>> getChildListBy(K key) {
    return Stream.of(getChildBy(key)).filter(c -> c != null).collect(Collectors.toList());
  }

  public void addChild(HashEntryTreeNode<K, V> child) {
    K key = child.getEntryKey();
    if (childMap.containsKey(key)) removeChildBy(key);

    addChild(getChildCount(), child);
  }

  @Override
  public void addChild(int idx, HashEntryTreeNode<K, V> child) {
    K key = child.getEntryKey();
    if (childMap.containsKey(key)) removeChildBy(key);

    if (idx == childMap.size())
      childMap.put(child.getEntryKey(), child);
    else
      childMap.put(idx, child.getEntryKey(), child);
    child.setParent(this);
  }

  public void putChild(HashEntryTreeNode<K, V> child) {
    putChild(childMap.size(), child);
  }

  public void putChild(int idx, HashEntryTreeNode<K, V> child) {
    K key = child.getEntryKey();
    if (childMap.containsKey(key)) childMap.remove(key);
    addChild(idx, child);
  }

  @Override
  public boolean removeChild(HashEntryTreeNode<K, V> child) {
    HashEntryTreeNode<K, V>  existChild = childMap.get(child.getEntryKey());
    if (existChild == child) {
      childMap.remove(child.getEntryKey());
      child.setParent(null);
      return true;
    } else {
      return false;
    }
  }

  public HashEntryTreeNode<K, V> removeChildAt(int idx) {
    return childMap.remove(idx);
  }

  @Override
  public List<HashEntryTreeNode<K, V>> removeAllChildBy(K key) {
    HashEntryTreeNode<K, V> child = childMap.remove(key);
    return Stream.of(child).filter(c -> c != null).collect(Collectors.toList());
  }

  @Override
  public Collection<HashEntryTreeNode<K, V>> getChildList() {
    return childMap.values();
  }

  @Override
  public int getChildCount() {
    return childMap.size();
  }

  @Override
  public HashEntryTreeNode<K, V> childAt(int index) {
    return childMap.getValue(index);
  }

  @Override
  public int indexOf(HashEntryTreeNode<K, V> child) {
    return childMap.indexOf(child.getEntryKey());
  }

  public void setValue(Entry<K, V> entry) {
    HashEntryTreeNode<K, V> parent = getParent();
    if (parent == null) {
      super.setValue(entry);
      return;
    } else {
      HashEntryTreeNode<K, V> existing = parent.getChildBy(entry.getKey());
      if (existing != null && existing != this) kvex(); //unique check
      else {
        super.setValue(entry);
      }
    }
  }

  public void clearChild() {
    childMap.valueList().forEach(c -> c.setParent(null));
    childMap.clear();
  }

  private void kvex() {
    throw new KeyViolationException();
  }
}
